//
//  RXFrameRichLabel.m
//  RXFrame
//
//  Created by srxboys on 2021/4/22.
//

#import "RXFrameRichLabel.h"

#define IMG_STR @"Bb"
typedef void (^LinkAction)(void);

@interface RichLabelLink : NSObject

@property (nonatomic, assign) NSRange linkRange;

@property (nonatomic, strong) NSDictionary *normalAttributes;

@property (nonatomic, strong) NSDictionary *highlightAttributes;

@property (nonatomic, copy) LinkAction linkAction;

@end

@implementation RichLabelLink

- (id)init {
    self = [super init];
    if (self) {

    }
    return self;
}
@end





@interface RXFrameRichLabel ()
{

    CTFramesetterRef _framesetter;
    BOOL _needsFramesetter;
}

@property (nonatomic, strong) NSMutableAttributedString *attributedString;

@property (nonatomic, assign) NSRange touchRange;

@property (nonatomic, strong) NSMutableArray *linkList;

@property (nonatomic, strong) RichLabelLink *activeLink;

@property (nonatomic, assign) CGRect textRect;

@end

@implementation RXFrameRichLabel
- (instancetype)init
{
    self = [super init];
    if (self) {
        self.userInteractionEnabled = YES;
        CGAffineTransform transform = CGAffineTransformMakeScale(1, -1);
        self.transform = transform;
        _framesetter = NULL;
        _linkList = [[NSMutableArray alloc] init];
        _needsFramesetter = YES;
        _iconSize = CGSizeMake(0, 0);
    }
    return self;
}

- (void)dealloc {
    if (_framesetter) {
        CFRelease(_framesetter);
        _framesetter = NULL;
    }
}

- (CTFramesetterRef)framesetter {
    if (_needsFramesetter) {
        @synchronized(self) {
            CFAttributedStringRef attributedString = (__bridge CFTypeRef)self.attributedString;
            CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString(attributedString);
            [self setFramesetter:frameSetter];
            _needsFramesetter = NO;

            if (frameSetter) {
                CFRelease(frameSetter);
            }
        }
    }

    return _framesetter;
}

- (void)setFramesetter:(CTFramesetterRef)framesetter {
    if (framesetter)
        CFRetain(framesetter);

    if (_framesetter)
        CFRelease(_framesetter);

    _framesetter = framesetter;
}

- (void)resetAttributedString {
    if ([self.text length] == 0)
        return ;

    self.attributedString = [[NSMutableAttributedString alloc] initWithString:self.text];

    if (!self.attributedString)
        return ;

    if ([self.iconName length] != 0) {
        self.attributedString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@%@", IMG_STR, self.text]];
        super.text = self.attributedString.string;

        NSRange imgRange = [self.text rangeOfString:IMG_STR];
        [self.attributedString addAttribute:@"img" value:self.iconName range:imgRange];
    }

    if (self.textColor) {
        [self.attributedString addAttribute:(id)kCTForegroundColorAttributeName value:(id)self.textColor.CGColor range:NSMakeRange(0, [self.text length])];
    }

    if ([self.iconName length] != 0) {
        NSRange imgRange = [self.text rangeOfString:IMG_STR];
        [self.attributedString addAttribute:(NSString *)kCTForegroundColorAttributeName value:[UIColor clearColor] range:imgRange];
    }

    [self.attributedString addAttribute:(id)kCTFontAttributeName value:self.font range:NSMakeRange(0, [self.text length])];
}

- (NSString *)text {
    return super.text;
}

- (void)setText:(NSString *)text {
    super.text = text;
    [self resetAttributedString];
    _needsFramesetter = YES;
}

- (void)setLinkWithText:(NSString *)text
                  index:(NSUInteger)index
             attributes:(NSDictionary *)attributes
              tapAction:(void (^)(void))tapAction {

    NSRange linkRange = NSMakeRange(0, 0);
    NSUInteger idx = 0;
    NSString *pText = self.text;
    while (linkRange.location != NSNotFound) {
        linkRange = [pText rangeOfString:text];
        if (idx == index)
            break;
        idx++;
        pText = [pText substringFromIndex:linkRange.location + linkRange.length];
    }

    if (linkRange.location == NSNotFound)
        return;

    RichLabelLink *link = [[RichLabelLink alloc] init];
    link.linkRange = linkRange;
    link.highlightAttributes = attributes;
    link.linkAction = tapAction;

    [self.linkList addObject:link];

    [link.highlightAttributes enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
        [self.attributedString addAttribute:key value:obj range:link.linkRange];
    }];
}

- (void)setHighlightText:(NSString *)highlightText
                   index:(NSUInteger)index
               withColor:(UIColor *)color
{
    NSRange range = NSMakeRange(0, 0);
    NSUInteger idx = 0;
    NSString *pText = self.text;
    while (range.location != NSNotFound)
    {
        range = [pText rangeOfString:highlightText];
        if (idx == index)
            break;
        idx++;
        pText = [pText substringFromIndex:range.location+range.length];
    }

    [self.attributedString addAttribute:(id)kCTForegroundColorAttributeName value:color range:range];
}

- (CGSize)sizeWithConstraint:(CGSize)constaintSize
{
    CTFramesetterRef  frameSetter = CTFramesetterCreateWithAttributedString((CFMutableAttributedStringRef)self.attributedString);

    CGSize textConstrainSize = CGSizeMake(constaintSize.width - self.contentInset.left - self.contentInset.right, constaintSize.height - self.contentInset.top - self.contentInset.bottom);

    CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints(frameSetter, CFRangeMake(0, [self.attributedString length]), NULL, textConstrainSize, NULL);
    if (frameSetter) {
        CFRelease(frameSetter);
    }

    CGSize size = CGSizeMake(textSize.width + self.contentInset.left + self.contentInset.right, textSize.height + self.contentInset.top + self.contentInset.bottom);
    return size;
}

- (void)setActiveLink:(RichLabelLink *)activeLink {
    RichLabelLink *preActiveLink = _activeLink;
    _activeLink = activeLink;

    if (!activeLink) {
        if (preActiveLink) {
            UIColor *color = [preActiveLink.highlightAttributes objectForKey:(NSString *)kCTForegroundColorAttributeName];
            [self.attributedString addAttribute:(id)kCTForegroundColorAttributeName value:color range:preActiveLink.linkRange];
            _needsFramesetter = YES;
            [self setNeedsDisplay];
        }

    } else {
        UIColor *hlColor = self.anchorTextHighlightColor ?: self.textColor;
        [self.attributedString addAttribute:(id)kCTForegroundColorAttributeName value:hlColor range:activeLink.linkRange];
        _needsFramesetter = YES;
        [self setNeedsDisplay];
        [CATransaction flush];
    }
}

- (void)drawTextInRect:(CGRect)rect
{
    if (self.hidden || [self.text length] == 0)
        return ;

    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);

    if (!context)
        return ;

    CGSize textSize = [self sizeWithConstraint:rect.size];

    CGFloat top = self.contentInset.top;
    CGFloat bottom = self.contentInset.bottom;
    CGFloat left = self.contentInset.left;
    CGFloat right = self.contentInset.right;

    if (rect.size.height > textSize.height + self.contentInset.top + self.contentInset.bottom)
    {
        top = (rect.size.height - textSize.height) / 2;
        bottom = (rect.size.height - textSize.height) / 2;
    }

    if (self.textAlignment == NSTextAlignmentLeft)
    {

    }
    else if (self.textAlignment == NSTextAlignmentRight)
    {
        left = rect.size.width - textSize.width - self.contentInset.right;
    }
    else if (self.textAlignment == NSTextAlignmentCenter)
    {
        left = (rect.size.width - textSize.width) / 2;
        right = (rect.size.width - textSize.width) / 2;
    }

    CGRect textRect = CGRectMake(left, bottom, rect.size.width - left-right, rect.size.height - top - bottom);

    self.textRect = textRect;

    CGPathRef path = CGPathCreateWithRect(textRect, NULL);

    CGContextTranslateCTM(context, 0, 0);

    CTFrameRef frame = CTFramesetterCreateFrame([self framesetter], CFRangeMake(0, 0), path, NULL);

    CFArrayRef allLines = CTFrameGetLines(frame);

    CGPoint allLineOrigins[CFArrayGetCount(allLines)];
    CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), allLineOrigins);

    BOOL preImage = NO;

    for (int i = 0; i < CFArrayGetCount(allLines); i++)
    {
        preImage = NO;
        CTLineRef currentLine = CFArrayGetValueAtIndex(allLines, i);
        CGPoint currentLineOrigin = allLineOrigins[i];

        CFArrayRef runsOfCurrentLine = CTLineGetGlyphRuns(currentLine);

        for (int j = 0; j < CFArrayGetCount(runsOfCurrentLine); j++)
        {
            CTRunRef currentRun = CFArrayGetValueAtIndex(runsOfCurrentLine, j);
            NSDictionary *attr = (NSDictionary *)CTRunGetAttributes(currentRun);

            //            CGFloat ascent;
            //            CGFloat descent;

            CGRect runBounds;

            if ([attr objectForKey:@"img"])
            {
                runBounds.origin.x = currentLineOrigin.x+CTLineGetOffsetForStringIndex(currentLine, CTRunGetStringRange(currentRun).location, NULL) + left;
                //                runBounds.origin.y = currentLineOrigin.y+bottom-1.5;
                //                runBounds.size.width = self.font.pointSize;
                //                runBounds.size.height = runBounds.size.width;
                NSString *iconName = (NSString *)[attr objectForKey:@"img"];
                UIImage *image = [UIImage imageNamed:iconName];

                if (self.iconSize.width > 1) {
                    runBounds.size.width = self.iconSize.width;
                    runBounds.size.height = self.iconSize.height;
                    runBounds.origin.y = currentLineOrigin.y + bottom - 1.5 + (self.font.pointSize - self.iconSize.height) / 2;
                } else {
                    runBounds.size.width = image.size.width;
                    runBounds.size.height = image.size.height;
                    runBounds.origin.y = currentLineOrigin.y + bottom - 1.5 + (self.font.pointSize - image.size.height) / 2;
                }

                if (image != nil)
                {
                    CGContextDrawImage(context, runBounds, image.CGImage);
                }
                preImage = YES;
            }
            //            else
            //            {
            //                runBounds.origin.x = currentLineOrigin.x+CTLineGetOffsetForStringIndex(currentLine, CTRunGetStringRange(currentRun).location, NULL);
            ////                if (preImage)
            ////                {
            ////                    runBounds.origin.x += self.font.pointSize*3/2;
            ////                    preImage = NO;
            ////                }
            //                runBounds.origin.y = currentLineOrigin.y;
            //                runBounds.size.width = CTRunGetTypographicBounds(currentRun, CFRangeMake(0,0), &ascent, &descent, NULL);
            //                runBounds.size.height = ascent+descent;
            //            }
        }
    }

    CTFrameDraw(frame, context);

    CFRelease(frame);
    CFRelease(path);
}

- (CFIndex)characterIndexAtPoint:(CGPoint)p
{
    if (CGRectContainsPoint(self.bounds, p) == NO)
        return NSNotFound;

    p.y -= self.textRect.origin.y;
    p.x -= self.textRect.origin.x;

    CFIndex index = NSNotFound;

    CGPathRef path = CGPathCreateWithRect(self.textRect, NULL);

    CTFrameRef frame = CTFramesetterCreateFrame([self framesetter], CFRangeMake(0, 0), path, NULL);

    CFArrayRef allLines = CTFrameGetLines(frame);

    CGPoint allLineOrigins[CFArrayGetCount(allLines)];
    CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), allLineOrigins);

    CTLineRef line = NULL;

    for (int i = 0; i < CFArrayGetCount(allLines); i++)
    {
        CGPoint lineOrigin = allLineOrigins[i];
        CTLineRef currentLine = CFArrayGetValueAtIndex(allLines, i);
        CGFloat ascent = 0.0f, descent = 0.0f, leading = 0.0f;
        CGFloat width = (CGFloat)CTLineGetTypographicBounds(currentLine, &ascent, &descent, &leading);
        CGFloat yMin = (CGFloat)floor(lineOrigin.y - descent);
        CGFloat yMax = (CGFloat)ceil(lineOrigin.y + ascent);

        if (p.y >= yMin && p.y < yMax)
        {
            if (p.x >= lineOrigin.x && p.x <= lineOrigin.x + width)
            {
                line = currentLine;
                lineOrigin = lineOrigin;
                break;
            }
        }
    }

    if (line != NULL)
    {
        index = CTLineGetStringIndexForPosition(line, p);
    }

    CFRelease(frame);
    CFRelease(path);


    return index - 1;
}

- (RichLabelLink *)linkAtPoint:(CGPoint)point {
    if (!CGRectContainsPoint(self.bounds, point) || self.linkList.count == 0)
    {
        return nil;
    }

    RichLabelLink *link = [self linkAtCharacterIndex:[self characterIndexAtPoint:point]];

    if (!link) {
        link = [self linkAtRadius:2.5f aroundPoint:point];
        if (!link)
            link = [self linkAtRadius:5.f aroundPoint:point];
        if (!link)
            link = [self linkAtRadius:7.5f aroundPoint:point];
        if (!link)
            link = [self linkAtRadius:7.5f aroundPoint:point];
        if (!link)
            link = [self linkAtRadius:12.5f aroundPoint:point];
        if (!link)
            link = [self linkAtRadius:15.f aroundPoint:point];
    }

    return link;
}

- (RichLabelLink *)linkAtCharacterIndex:(CFIndex)index {
    if (index == NSNotFound)
        return nil;

    for (RichLabelLink *link in self.linkList) {
        if (NSLocationInRange(index, link.linkRange))
            return link;
    }

    return nil;
}

- (RichLabelLink *)linkAtRadius:(const CGFloat)radius aroundPoint:(CGPoint)point
{
    //    const CGFloat diagonal = CGFloat_sqrt(2 * radius * radius);
    //    const CGPoint deltas[] = {
    //        CGPointMake(0, -radius), CGPointMake(0, radius), // Above and below
    //        CGPointMake(-radius, 0), CGPointMake(radius, 0), // Beside
    //        CGPointMake(-diagonal, -diagonal), CGPointMake(-diagonal, diagonal),
    //        CGPointMake(diagonal, diagonal), CGPointMake(diagonal, -diagonal) // Diagonal
    //    };
    //    const size_t count = sizeof(deltas) / sizeof(CGPoint);
    //
    //    RichLabelLink *link = nil;
    //
    //    for (NSInteger i = 0; i < count; i ++)
    //    {
    //        CGPoint currentPoint = CGPointMake(point.x + deltas[i].x, point.y + deltas[i].y);
    //        link = [self linkAtCharacterIndex:[self characterIndexAtPoint:currentPoint]];
    //    }
    RichLabelLink *link = nil;

    link = [self linkAtCharacterIndex:[self characterIndexAtPoint:CGPointMake(point.x, point.y + radius)]];
    if (!link)
        link = [self linkAtCharacterIndex:[self characterIndexAtPoint:CGPointMake(point.x, point.y - radius)]];
    if (!link)
        link = [self linkAtCharacterIndex:[self characterIndexAtPoint:CGPointMake(point.x - radius, point.y)]];
    if (!link)
        link = [self linkAtCharacterIndex:[self characterIndexAtPoint:CGPointMake(point.x + radius, point.y)]];

    return link;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];

    CGPoint location = [touch locationInView:self];

    self.activeLink = [self linkAtPoint:location];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint location = [touch locationInView:self];

    RichLabelLink *link = [self linkAtPoint:location];

    if (self.activeLink == link && self.activeLink.linkAction) {
        self.activeLink.linkAction();
    }
    self.activeLink = nil;
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];

    CGPoint location = [touch locationInView:self];

    if (self.activeLink != [self linkAtPoint:location]) {

        self.activeLink = nil;
    }
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {

    self.activeLink = nil;
}

/*
 // Only override drawRect: if you perform custom drawing.
 // An empty implementation adversely affects performance during animation.
 - (void)drawRect:(CGRect)rect {
 // Drawing code
 }
 */

@end
